package cn.com.infcn.httpclient.lessons1;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.ssl.SSLException;

import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.ParseException;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

/**
 * 第一章 基本概念
 * 
 * @author jjs
 *
 */
public class HttpClient1 {

	/**
	 * 1.1. 请求执行
	 * 
	 * HttpClient最基本的功能就是执行Http方法。一个Http方法的执行涉及到一个或者多个Http请求/Http响应的交互，
	 * 通常这个过程都会自动被HttpClient处理
	 * ，对用户透明。用户只需要提供Http请求对象，HttpClient就会将http请求发送给目标服务器，
	 * 并且接收服务器的响应，如果http请求执行不成功，httpclient就会抛出异样。
	 * 
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static void test1() throws ClientProtocolException, IOException {
		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpGet httpGet = new HttpGet("http://www.infcn.com.cn");
		CloseableHttpResponse response = httpClient.execute(httpGet);

		try {
			Header[] headers = response.getAllHeaders();
			for (Header header : headers) {
				System.out.println(header.getName() + ":" + header.getValue());
			}

			System.out.println("=======================");
			//
			Locale locale = response.getLocale();
			System.out.println(locale.getDisplayName());
			System.out.println(locale.getCountry());
			System.out.println(locale.getDisplayLanguage());
			System.out.println(locale.getDisplayScript());
			System.out.println(locale.getDisplayVariant());
			System.out.println(locale.getLanguage());
			System.out.println(locale.getScript());

			// 状态行
			StatusLine sl = response.getStatusLine();
			System.out.println(sl.getStatusCode() + "==" + sl.getProtocolVersion());

			response.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 1.1.1. HTTP请求
	 * 
	 * 所有的Http请求都有一个请求行(request line)，包括方法名、请求的URI和Http版本号。
	 * HttpClient支持HTTP/1.1这个版本定义的所有Http方法
	 * ：GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS
	 * 。对于每一种http方法，HttpClient都定义了一个相应的类：HttpGet, HttpHead, HttpPost, HttpPut,
	 * HttpDelete, HttpTrace和HttpOpquertions。
	 * Request-URI即统一资源定位符，用来标明Http请求中的资源。Http request
	 * URIs包含协议名、主机名、主机端口（可选）、资源路径、query（可选）和片段信息（可选）。
	 * 
	 * @param args
	 * @throws URISyntaxException
	 * @throws Exception
	 */
	public static void test2() throws URISyntaxException {
		HttpGet httpGet = new HttpGet("http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
		URI uri = new URIBuilder().setScheme("http").setHost("www.google.com").setPath("/search")
				.setParameter("q", "httpclient").setParameter("btnG", "Google Search").setParameter("aq", "f")
				.setParameter("oq", "").build();
		httpGet = new HttpGet(uri);
		System.out.println(httpGet.getURI());
	}

	/**
	 * 1.1.2. HTTP响应
	 * 
	 * 服务器收到客户端的http请求后，就会对其进行解析，然后把响应发给客户端，这个响应就是HTTP
	 * response.HTTP响应第一行是协议版本，之后是数字状态码和相关联的文本段。
	 */
	public static void test3() {
		HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "ok");

		System.out.println(response.getProtocolVersion());
		System.out.println(response.getStatusLine().getStatusCode());
		System.out.println(response.getStatusLine().getReasonPhrase());
	}

	/**
	 * 1.1.3. 消息头
	 * 
	 * 一个Http消息可以包含一系列的消息头，用来对http消息进行描述，比如消息长度，消息类型等等。HttpClient提供了方法来获取、添加、移除、
	 * 枚举消息头。
	 */
	public static void test4() {
		HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
		response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
		response.addHeader("Set-Cookie",
				"c1=a;  path=\"/\"; domain=\"localhost\", c2=b; path=\"/\", c3=c; domain=\"localhost\"");
		Header h1 = response.getFirstHeader("Set-Cookie");
		System.out.println(h1);
		Header h2 = response.getLastHeader("Set-Cookie");
		System.out.println(h2);
		Header[] headers = response.getHeaders("Set-Cookie");
		System.out.println(headers.length);

		// 最有效的获取指定类型的消息头的方法还是使用HeaderIterator接口。
		HeaderIterator it = response.headerIterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}

		HeaderElementIterator eleIt = new BasicHeaderElementIterator(response.headerIterator());
		while (eleIt.hasNext()) {
			HeaderElement elem = eleIt.nextElement();
			System.out.println(elem.getName() + " = " + elem.getValue());

			NameValuePair[] params = elem.getParameters();
			for (int i = 0; i < params.length; i++) {
				System.out.println("\t" + params[i]);
			}
		}
	}

	/**
	 * 1.1.4. HTTP实体
	 * 
	 * Http消息可以携带http实体，这个http实体既可以是http请求，也可以是http响应的。Http实体，可以在某些http请求或者响应中发现
	 * ，但不是必须的。Http规范中定义了两种包含请求的方法：POST和PUT。HTTP响应一般会包含一个内容实体。当然这条规则也有异常情况，
	 * 如Head方法的响应，204没有内容，304没有修改或者205内容资源重置。
	 * HttpClient根据来源的不同，划分了三种不同的Http实体内容。 streamed流式:
	 * 内容是通过流来接受或者在运行中产生。特别是，streamed这一类包含从http响应中获取的实体内容。一般说来，streamed实体是不可重复的。
	 * self-contained自我包含式:内容在内存中或通过独立的连接或其它实体中获得。self-contained类型的实体内容通常是可重复的。
	 * 这种类型的实体通常用于关闭http请求。 wrapping包装式: 这种类型的内容是从另外的http实体中获取的。
	 * 当从Http响应中读取内容时，上面的三种区分对于连接管理器来说是非常重要的
	 * 。对于由应用程序创建而且只使用HttpClient发送的请求实体，streamed和self
	 * -contained两种类型的不同就不那么重要了。这种情况下
	 * ，建议考虑如streamed流式这种不能重复的实体，和可以重复的self-contained自我包含式实体。
	 * 
	 * 1.1.4.1. 可重复的实体
	 * 
	 * 一个实体是可重复的，也就是说它的包含的内容可以被多次读取。这种多次读取只有self
	 * contained（自包含）的实体能做到（比如ByteArrayEntity或者StringEntity)。
	 * 
	 * 1.1.4.2. 使用Http实体
	 * 
	 * 由于一个Http实体既可以表示二进制内容，又可以表示文本内容，所以Http实体要支持字符编码（为了支持后者，即文本内容）。
	 * 当需要执行一个完整内容的Http请求或者Http请求已经成功，服务器要发送响应到客户端时，Http实体就会被创建。
	 * 如果要从Http实体中读取内容，我们可以利用HttpEntity类的getContent方法
	 * 来获取实体的输入流（java.io.InputStream)，或者利用HttpEntity类的
	 * writeTo(OutputStream)方法来获取输出流，这个方法会把所有的内容写入到给定的流中。 当实体类已经被接受后
	 * ，我们可以利用HttpEntity类的getContentType() 和getContentLength()方法来读取Content
	 * -Type和Content -Length两个头消息（如果有的话）。由于Content-Type包含mime-types的字符编码，
	 * 比如text/plain或者text/html,HttpEntity类的getContentEncoding()方法就是读取这个编码的。
	 * 如果头信息不存在，getContentLength（）会返回-1,getContentType()会返回NULL。
	 * 如果Content-Type信息存在，就会返回一个Header类。 当为发送消息创建Http实体时，需要同时附加meta信息。
	 * 
	 * @throws ParseException
	 * @throws IOException
	 */
	public static void test5() throws ParseException, IOException {
		StringEntity myEntity = new StringEntity("important message", ContentType.create("text/plain", "UTF-8"));
		System.out.println(myEntity.getContentType());
		System.out.println(myEntity.getContentLength());
		System.out.println(EntityUtils.toString(myEntity));
		System.out.println(EntityUtils.toByteArray(myEntity).length);
	}

	/**
	 * 1.1.5. 确保底层的资源连接被释放
	 * 
	 * 为了确保系统资源被正确地释放，我们要么管理Http实体的内容流、要么关闭Http响应。
	 * 
	 * 关闭Http实体内容流和关闭Http响应的区别在于，前者通过消耗掉Http实体内容来保持相关的http连接，然后后者会立即关闭、丢弃http连接。
	 * 
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static void test6() throws ClientProtocolException, IOException {
		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpGet httpGet = new HttpGet("http://www.infcn.com.cn");
		CloseableHttpResponse response = httpClient.execute(httpGet);
		try {
			HttpEntity entity = response.getEntity();
			InputStream inputStream = entity.getContent();
			try {
				byte[] b = new byte[1024];
				while (inputStream.read(b) != -1) {
					System.out.println(new String(b));
				}
			} catch (Exception e) {
			} finally {
				inputStream.close();
			}
		} catch (Exception e) {
		} finally {
			response.close();
			httpClient.close();
		}

		/**
		 * 关闭Http实体内容流和关闭Http响应的区别在于，前者通过消耗掉Http实体内容来保持相关的http连接，然后后者会立即关闭、
		 * 丢弃http连接。
		 * 请注意HttpEntity的writeTo(OutputStream)方法，当Http实体被写入到OutputStream后
		 * ，也要确保释放系统资源
		 * 。如果这个方法内调用了HttpEntity的getContent()方法，那么它会有一个java.io.InpputStream的实例
		 * ，我们需要在finally中关闭这个流。
		 * 但是也有这样的情况，我们只需要获取Http响应内容的一小部分，而获取整个内容并、实现连接的可重复性代价太大
		 * ，这时我们可以通过关闭响应的方式来关闭内容输入、输出流。
		 */
		httpClient = HttpClients.createDefault();
		httpGet = new HttpGet("http://localhost/");
		response = httpClient.execute(httpGet);
		try {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				InputStream instream = entity.getContent();
				int byteOne = instream.read();
				int byteTwo = instream.read();
				// Do not need the rest
			}
		} finally {
			response.close();
		}
	}

	/**
	 * 1.1.6. 消耗HTTP实体内容
	 * 
	 * HttpClient推荐使用HttpEntity的getConent()方法或者HttpEntity的writeTo(OutputStream)
	 * 方法来消耗掉Http实体内容
	 * 。HttpClient也提供了EntityUtils这个类，这个类提供一些静态方法可以更容易地读取Http实体的内容和信息
	 * 。和以java.io.InputStream流读取内容的方式相比
	 * ，EntityUtils提供的方法可以以字符串或者字节数组的形式读取Http实体。但是
	 * ，强烈不推荐使用EntityUtils这个类，除非目标服务器发出的响应是可信任的，并且http响应实体的长度不会过大。
	 * 
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static void test7() throws ClientProtocolException, IOException {
		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpGet httpGet = new HttpGet("http://localhost:8080/examples/index.html");
		CloseableHttpResponse response = httpClient.execute(httpGet);
		try {
			HttpEntity entity = response.getEntity();

			/**
			 * 有些情况下，我们希望可以重复读取Http实体的内容。这就需要把Http实体内容缓存在内存或者磁盘上。最简单的方法就是把Http
			 * Entity转化成BufferedHttpEntity
			 * ，这样就把原Http实体的内容缓冲到了内存中。后面我们就可以重复读取BufferedHttpEntity中的内容。
			 */
			if (entity != null) {
				entity = new BufferedHttpEntity(entity);
			}

			if (entity != null) {
				long len = entity.getContentLength();
				if (len != -1 && len < 2048) {
					System.out.println(EntityUtils.toString(entity));
					System.out.println("-------entityUtils--------------");
				} else {
					InputStream inputStream = entity.getContent();
					try {
						byte[] b = new byte[1024];
						while (inputStream.read(b) != -1) {
							System.out.println(new String(b));
						}
					} catch (Exception e) {
					} finally {
						inputStream.close();
					}
					System.out.println("------------inputStrem-----------");
				}
				System.out.println(len);
			}

		} catch (Exception e) {
			// TODO: handle exception
		} finally {
			response.close();
			httpClient.close();
		}
	}

	/**
	 * 1.1.7. 创建HTTP实体内容
	 * HttpClient提供了一些类，这些类可以通过http连接高效地输出Http实体内容。HttpClient提供的这几个类涵盖的常见的数据类型
	 * ，如String，byte数组，输入流，和文件类型：StringEntity,ByteArrayEntity,InputStreamEntity,
	 * FileEntity。
	 * 
	 * 请注意由于InputStreamEntity只能从下层的数据流中读取一次，所以它是不能重复的。推荐，
	 * 通过继承HttpEntity这个自包含的类来自定义HttpEntity类
	 * ，而不是直接使用InputStreamEntity这个类。FileEntity就是一个很好的起点
	 * （FileEntity就是继承的HttpEntity）。
	 * 
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static void test8() throws ClientProtocolException, IOException {
		File file = new File("somefile.txt");
		FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8"));
		HttpPost httpPost = new HttpPost("http://localhost:8080/httpclient-web/login");
		httpPost.setEntity(entity);

	}

	/**
	 * 1.7.1.1. HTML表单
	 * 
	 * 很多应用程序需要模拟提交Html表单的过程，举个例子，登陆一个网站或者将输入内容提交给服务器。
	 * HttpClient提供了UrlEncodedFormEntity这个类来帮助实现这一过程。
	 * 
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static void test9() throws ClientProtocolException, IOException {
		CloseableHttpClient httpClient = HttpClients.createDefault();
		List<NameValuePair> formParams = new ArrayList<NameValuePair>();
		formParams.add(new BasicNameValuePair("userName", "admin"));
		formParams.add(new BasicNameValuePair("password", "123456"));
		UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
		HttpPost httpPost = new HttpPost("http://localhost:8080/httpclient-web/login");
		httpPost.setEntity(entity);
		CloseableHttpResponse response = httpClient.execute(httpPost);

		HttpEntity httpEntity = response.getEntity();
		if (httpEntity != null) {
			long len = httpEntity.getContentLength();
			if (len != -1 && len < 2048) {
				System.out.println(EntityUtils.toString(httpEntity));
				System.out.println("-------entityUtils--------------");
			} else {
				InputStream inputStream = httpEntity.getContent();
				try {
					byte[] b = new byte[1024];
					while (inputStream.read(b) != -1) {
						System.out.println(new String(b));
					}
				} catch (Exception e) {
				} finally {
					inputStream.close();
				}
				System.out.println("------------inputStrem-----------");
			}
			System.out.println(len);
		}

		HeaderIterator it = response.headerIterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}

	}

	/**
	 * 1.2. HttpClient接口
	 * 
	 * 对于Http请求执行过程来说，HttpClient的接口有着必不可少的作用。
	 * HttpClient接口没有对Http请求的过程做特别的限制和详细的规定
	 * ，连接管理、状态管理、授权信息和重定向处理这些功能都单独实现。这样用户就可以更简单地拓展接口的功能（比如缓存响应内容）。
	 * 一般说来，HttpClient实际上就是一系列特殊的handler或者说策略接口的实现
	 * ，这些handler（测试接口）负责着处理Http协议的某一方面，比如重定向、认证处理、有关连接持久性和keep
	 * alive持续时间的决策。这样就允许用户使用自定义的参数来代替默认配置，实现个性化的功能。
	 * 
	 * @throws Exception
	 */
	public static void test10() throws Exception {
		ConnectionKeepAliveStrategy keepAliveStrategy = new DefaultConnectionKeepAliveStrategy() {

			@Override
			public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
				long keepAlive = super.getKeepAliveDuration(response, context);
				if (keepAlive == -1) {
					keepAlive = 5000;
				}
				return keepAlive;
			}
		};

		// 设置长连接
		CloseableHttpClient httpClient = HttpClients.custom().setKeepAliveStrategy(keepAliveStrategy).build();

		/**
		 * 当一个CloseableHttpClient的实例不再被使用，并且它的作用范围即将失效，和它相关的连接必须被关闭，
		 * 关闭方法可以调用CloseableHttpClient的close()方法。
		 */
		httpClient.close();
	}

	/**
	 * 1.3.Http执行上下文
	 * 
	 * 最初，Http被设计成一种无状态的、面向请求-响应的协议。然而，在实际使用中，我们希望能够在一些逻辑相关的请求-响应中，保持状态信息。
	 * 为了使应用程序可以保持Http的持续状态
	 * ，HttpClient允许http连接在特定的Http上下文中执行。如果在持续的http请求中使用了同样的上下文
	 * ，那么这些请求就可以被分配到一个逻辑会话中。HTTP上下文就和一个java.util.Map<String,
	 * Object>功能类似。它实际上就是一个任意命名的值的集合。应用程序可以在Http请求执行前填充上下文的值，也可以在请求执行完毕后检查上下文。
	 * HttpContext可以包含任意类型的对象，因此如果在多线程中共享上下文会不安全。推荐每个线程都只包含自己的http上下文。
	 * 在Http请求执行的过程中，HttpClient会自动添加下面的属性到Http上下文中：
	 * HttpConnection的实例，表示客户端与服务器之间的连接 HttpHost的实例，表示要连接的目标服务器
	 * HttpRoute的实例，表示全部的连接路由
	 * HttpRequest的实例，表示Http请求。在执行上下文中，最终的HttpRequest对象会代表http消息的状态
	 * 。Http/1.0和Http/1.1都默认使用相对的uri。但是如果使用了非隧道模式的代理服务器，就会使用绝对路径的uri。
	 * HttpResponse的实例，表示Http响应 java.lang.Boolean对象，表示是否请求被成功的发送给目标服务器
	 * RequestConfig对象，表示http request的配置信息
	 * java.util.List<Uri>对象，表示Http响应中的所有重定向地址
	 * 我们可以使用HttpClientContext这个适配器来简化和上下文交互的过程。
	 * 
	 * @throws Exception
	 */
	public static void test11() throws Exception {
		HttpContext context = new BasicHttpContext();
		HttpClientContext clientContext = HttpClientContext.adapt(context);
		HttpHost target = clientContext.getTargetHost();
		HttpRequest request = clientContext.getRequest();
		HttpResponse response = clientContext.getResponse();
		RequestConfig config = clientContext.getRequestConfig();
	}

	public static void test12() throws Exception {
		HttpContext context = new BasicHttpContext();
		HttpClientContext clientContext = HttpClientContext.adapt(context);
		ConnectionKeepAliveStrategy keepAliveStrategy = new DefaultConnectionKeepAliveStrategy() {
			@Override
			public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
				long keepAlive = super.getKeepAliveDuration(response, context);
				if (keepAlive == -1) {
					keepAlive = 5000;
				}
				return keepAlive;
			}
		};
		CloseableHttpClient httpClient = HttpClients.custom().setKeepAliveStrategy(keepAliveStrategy).build();

		// 未登录访问登录后的页面，显示请登录
		System.out.println("------------1---------------");
		HttpPost httpPost1 = new HttpPost("http://localhost:8080/httpclient-web/success.jsp");
		HttpResponse response1 = httpClient.execute(httpPost1, clientContext);
		HttpEntity httpEntity1 = response1.getEntity();
		System.out.println(EntityUtils.toString(httpEntity1));

		HttpHost target = clientContext.getTargetHost();
		HttpRequest request = clientContext.getRequest();
		HttpResponse response = clientContext.getResponse();
		RequestConfig config = clientContext.getRequestConfig();

		// 模拟登录
		System.out.println("------------2---------------");
		HttpPost httpPost2 = new HttpPost("http://localhost:8080/httpclient-web/login");

		List<NameValuePair> params = new ArrayList<NameValuePair>();
		NameValuePair userName = new BasicNameValuePair("userName", "admin");
		NameValuePair password = new BasicNameValuePair("password", "123456");
		params.add(userName);
		params.add(password);

		UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, Consts.UTF_8);

		httpPost2.setEntity(entity);

		HttpResponse response2 = httpClient.execute(httpPost2, clientContext);
		HttpEntity httpEntity2 = response2.getEntity();
		System.out.println(EntityUtils.toString(httpEntity2));

		target = clientContext.getTargetHost();
		request = clientContext.getRequest();
		response = clientContext.getResponse();
		config = clientContext.getRequestConfig();

		// 登录后请求登录后的成功页面，显示用户信息，说明 context 自动封装session 信息
		System.out.println("-------------3--------------");
		HttpPost httpPost3 = new HttpPost("http://localhost:8080/httpclient-web/success.jsp");
		HttpResponse response3 = httpClient.execute(httpPost3, clientContext);
		HttpEntity httpEntity3 = response3.getEntity();
		System.out.println(EntityUtils.toString(httpEntity3));
	}

	/**
	 * 1.4. 异常处理
	 * 
	 * HttpClient会被抛出两种类型的异常，一种是java.io.IOException，当遇到I/O异常时抛出（socket超时，
	 * 或者socket被重置）;另一种是HttpException,表示Http失败，如Http协议使用不正确。通常认为，I/O错误时不致命、可修复的，
	 * 而Http协议错误是致命了，不能自动修复的错误。
	 * 
	 * 1.4.1.HTTP传输安全
	 * 
	 * Http协议不能满足所有类型的应用场景，我们需要知道这点。Http是个简单的面向协议的请求/响应的协议，
	 * 当初它被设计用来支持静态或者动态生成的内容检索
	 * ，之前从来没有人想过让它支持事务性操作。例如，Http服务器成功接收、处理请求后，生成响应消息，并且把状态码发送给客户端
	 * ，这个过程是Http协议应该保证的
	 * 。但是，如果客户端由于读取超时、取消请求或者系统崩溃导致接收响应失败，服务器不会回滚这一事务。如果客户端重新发送这个请求
	 * ，服务器就会重复的解析、执行这个事务。在一些情况下，这会导致应用程序的数据损坏和应用程序的状态不一致。
	 * 即使Http当初设计是不支持事务操作，但是它仍旧可以作为传输协议为某些关键程序提供服务
	 * 。为了保证Http传输层的安全性，系统必须保证应用层上的http方法的幂等性。
	 * 
	 * 1.4.2.方法的幂等性
	 * 
	 * HTTP/1.1规范中是这样定义幂等方法的，Methods can also have the property of “idempotence”
	 * in that (aside from error or expiration issues) the side-effects of N > 0
	 * identical requests is the same as for a single
	 * request。用其他话来说，应用程序需要正确地处理同一方法多次执行造成的影响。添加一个具有唯一性的id就能避免重复执行同一个逻辑请求，问题解决。
	 * 请知晓，这个问题不只是HttpClient才会有，基于浏览器的应用程序也会遇到Http方法不幂等的问题。
	 * HttpClient默认把非实体方法get、head方法看做幂等方法，把实体方法post、put方法看做非幂等方法。
	 * 
	 * 1.4.3.异常自动修复
	 * 
	 * 默认情况下，HttpClient会尝试自动修复I/O异常。这种自动修复仅限于修复几个公认安全的异常。
	 * HttpClient不会尝试修复任何逻辑或者http协议错误（即从HttpException衍生出来的异常）。
	 * HttpClient会自动再次发送幂等的方法（如果首次执行失败)。
	 * HttpClient会自动再次发送遇到transport异常的方法，前提是Http请求仍旧保持着连接
	 * （例如http请求没有全部发送给目标服务器，HttpClient会再次尝试发送）。
	 * 
	 * 1.4.4.请求重试HANDLER
	 * 
	 * 如果要自定义异常处理机制，我们需要实现HttpRequestRetryHandler接口。
	 * 
	 * @throws Exception
	 */
	public static void test13() throws Exception {

		HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {

			@Override
			public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {

				if (executionCount >= 5) {
					// Do not retry if over max retry count
					// 如果超过最大重试次数，那么就不要继续了
					return false;
				}
				if (exception instanceof InterruptedIOException) {
					// Timeout
					return false;
				}
				if (exception instanceof UnknownHostException) {
					// Unknown host
					return false;
				}
				if (exception instanceof NoHttpResponseException) {
					// 如果服务器丢掉了连接，那么就重试
					return true;
				}
				if (exception instanceof ConnectTimeoutException) {
					// Connection refused
					return false;
				}
				if (exception instanceof SSLException) {
					// SSL handshake exception
					// 不要重试SSL握手异常
					return false;
				}
				HttpClientContext clientContext = HttpClientContext.adapt(context);
				HttpRequest request = clientContext.getRequest();
				boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
				if (idempotent) {
					// Retry if the request is considered idempotent
					// 如果请求被认为是幂等的，那么就重试
					return true;
				}
				return false;
			}
		};

		CloseableHttpClient httpClient = HttpClients.custom().setRetryHandler(myRetryHandler).build();

	}

	/**
	 * 1.6. Http协议拦截器
	 * 
	 * HTTP协议拦截器是一种实现一个特定的方面的HTTP协议的代码程序。通常情况下，协议拦截器会将一个或多个头消息加入到接受或者发送的消息中。
	 * 协议拦截器也可以操作消息的内容实体
	 * —消息内容的压缩/解压缩就是个很好的例子。通常，这是通过使用“装饰”开发模式，一个包装实体类用于装饰原来的实体来实现
	 * 。一个拦截器可以合并，形成一个逻辑单元。
	 * 协议拦截器可以通过共享信息协作——比如处理状态——通过HTTP执行上下文。协议拦截器可以使用Http上下文存储一个或者多个连续请求的处理状态。
	 * 通常，只要拦截器不依赖于一个特定状态的http上下文，那么拦截执行的顺序就无所谓。如果协议拦截器有相互依赖关系，必须以特定的顺序执行，
	 * 那么它们应该按照特定的顺序加入到协议处理器中。
	 * 协议处理器必须是线程安全的。类似于servlets，协议拦截器不应该使用变量实体，除非访问这些变量是同步的（线程安全的）。
	 * 下面是个例子，讲述了本地的上下文时如何在连续请求中记录处理状态的：
	 * 
	 * @throws Exception
	 */
	public static void test14() throws Exception {
		CloseableHttpClient httpClient = HttpClients.custom().addInterceptorLast(new HttpRequestInterceptor() {

			@Override
			public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
				AtomicInteger count = (AtomicInteger) context.getAttribute("count");
				request.addHeader("Count", Integer.toString(count.getAndIncrement()));
			}
		}).build();

		AtomicInteger count = new AtomicInteger();
		HttpClientContext localContext = HttpClientContext.create();
		localContext.setAttribute("count", count);

		HttpGet httpGet = new HttpGet("http://localhost:8080/httpclient-web/index.jsp");

		for (int i = 0; i < 10; i++) {
			CloseableHttpResponse response = httpClient.execute(httpGet, localContext);
			try {
				HttpEntity entity = response.getEntity();
				System.out.println(EntityUtils.toString(entity));
			} finally {
				response.close();
			}
		}
	}

	/**
	 * 1.7.1. 重定向处理
	 * 
	 * HttpClient会自动处理所有类型的重定向，除了那些Http规范明确禁止的重定向。See Other (status code 303)
	 * redirects on POST and PUT requests are converted to GET requests as
	 * required by the HTTP specification.
	 * 我们可以使用自定义的重定向策略来放松Http规范对Post方法重定向的限制。
	 * 
	 * 
	 * HttpClient在请求执行过程中，经常需要重写请求的消息。
	 * HTTP/1.0和HTTP/1.1都默认使用相对的uri路径。同样，原始的请求可能会被一次或者多次的重定向
	 * 。最终结对路径的解释可以使用最初的请求和上下文
	 * 。URIUtils类的resolve方法可以用于将拦截的绝对路径构建成最终的请求。这个方法包含了最后一个分片标识符或者原始请求。
	 * 
	 * @throws Exception
	 */
	public static void test15() throws Exception {
		HttpClientContext context = HttpClientContext.create();

		// 重定向处理策略
		LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();

		CloseableHttpClient httpClient = HttpClients.custom().setRedirectStrategy(redirectStrategy).build();
		List<NameValuePair> formParams = new ArrayList<NameValuePair>();
		formParams.add(new BasicNameValuePair("userName", "admin"));
		formParams.add(new BasicNameValuePair("password", "1234561"));
		UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
		HttpPost httpPost = new HttpPost("http://localhost:8080/httpclient-web/login");
		httpPost.setEntity(entity);
		CloseableHttpResponse response = httpClient.execute(httpPost, context);

		HttpEntity httpEntity = response.getEntity();
		System.out.println(EntityUtils.toString(httpEntity));

		try {
			HttpHost target = context.getTargetHost();
			List<URI> redirectLocations = context.getRedirectLocations();
			URI location = URIUtils.resolve(httpPost.getURI(), target, redirectLocations);
			System.out.println("Final HTTP location : " + location.toASCIIString());
		} catch (Exception e) {
			// TODO: handle exception
		}

		HeaderIterator it = response.headerIterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}

	public static void main(String[] args) throws Exception {
		// test1();
		// test2();
		// test3();
		// test4();
		// test5();
		// test6();
		// test7();
		// test8();
		// test9();
		// test10();
		// test11();
		// test12();
		// test13();
		// test14();
		test15();
	}
}
